持久层框架封装了jdbc的底层代码,我们需要提供的即sql、映射规则、pojo
研究mybatis,即
- mybatis怎么读取配置文件创建SqlSessionFactory
- SqlSession运行过程
这里更多探究的是SqlSession运行过程
第一点 - mybatis 源码分析–sql大致执行流程
[TOC]
一.准备
1. 映射器组成
- MappedStatement,用于保存映射器的一个节点(select|insert|delete|update),包括许多配置的sql、sql的id,缓存信息等
- SqlSource,提供BoundSql对象,是MappedStatement的属性。负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
BoundSql,表示动态生成的SQL语句以及相应的参数信息。常用属性:SQL、parameterObject、parameterMappings
parameterObject为传入的参数本身
parameterMappings,它是一个List,装parameterMapping对象,包括参数属性,名称,表达式,javaType,jdbcType、typeHandler等信息
sql,既是写在映射器中的一条SQL
2.SqlSession四大对象
Mapper执行过程是通过Executor、StatementHandler、ParameterHandler、ResultHandler来完成数据库操作和结果返回的
- Executor 执行器,调度StatementHandler、ParameterHandler、ResultHandler等来执行对应SQL
- StatementHandler 数据库会话器,使用数据库的Statement、PreparedStatement执行操作,核心
- ParameterHandler 参数处理器,用于SQL对参数的处理
- ResultHandler 结果处理器,对数据集的封装返回
二.SqlSession运行过程
SqlSession用途
- 获取映射器
- 直接通过命名信息去执行SQL返回结果
推荐使用映射器+xml方式1. 映射器动态代理对象执行过程
调用SqlSession中的getMapper方法,传入类对象。会通过类对象获得对应的MapperProxyFactory,构建实例,动态代理MapperProxy为通知类。生成动态代理对象,代理方法放到MapperProxy中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class MapperProxy<T> implements InvocationHandler, Serializable {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
if (this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
}
判断,如果是接口,则调用下面的方法,判断如果是默认的方法,如toString等方法,则直接调用。生成MapperMethod方法对象,执行该对象的execute方法传入sqlSession和当前参数,在该execute中进行具体的调用
1 | public Object execute(SqlSession sqlSession, Object[] args) { |
MappedStatement有SqlCommandType属性(存是select/insert等类型),在MappedMethod构造时,SqlCommandType放进MappedMethod内部的SqlCommand中的type(xml中的标签类型)来判断执行那一段方法,如为查询语句时即SELECT,根据返回类型来判断具体执行哪个方法
举例:执行查询语句,调用到executeForMany方法
1 | public class MapperMethod { |
最后通过SqlSession调用 ,通过MapperMethod的SqlCommand属性调用getName方法获得接口名+方法名的调用,探究的关键就在于MapperMethod在创建的时候就在SqlCommand的name中初始化好了名称,具体实现如下,看MapperProxy的invoke中的MapperMethod mapperMethod = this.cachedMapperMethod(method);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
this.methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
//MapperMethod的构造
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
}
//SqlCommand的构造
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
String methodName = method.getName();
Class<?> declaringClass = method.getDeclaringClass();
MappedStatement ms = this.resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
if (ms == null) {
if (method.getAnnotation(Flush.class) == null) {
throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName);
}
this.name = null;
this.type = SqlCommandType.FLUSH;
} else {
this.name = ms.getId();//这里将MappedStatement中的id属性进行赋值给name:接口名+方法名
this.type = ms.getSqlCommandType();
if (this.type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + this.name);
}
}
}
MapperMethod的内部类MethodSignature,主要作用是对Method方法做一步封装,比如: 提供判断方法返回值是否为NULL或Map等方法,及提供处理接口传入参数,封装转换成SqlSession所要的参数
在SqlCommand中,两个存放的信息,name 负责存放调用的目标方法名 ,type 负责存放SQL语句的类型(SELECT|INSERT|UPDATE等)。
通过SqlCommand构造函数可以看到,this.name = ms.getId();
其对name进行了赋值,值为对应xml文件中对应方法的id,怎么找到的这个MappedStatement的?
看这行代码MappedStatement ms = this.resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
1 | private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass, Configuration configuration) { |
可以看到,mybaits将调用的接口名与方法名的字符串,在MappedStatements中找对应的xml中对应的MappedStatement结点,从而调用到对应的xml写的sql。(MappedStatements是Configuration的一个属性protected final Map<String, MappedStatement> mappedStatements;
)
2. 底层执行过程
在创建SqlSession时,已经创建了Executor1
2
3public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
具体的创建方法如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? this.defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Object executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (this.cacheEnabled) {
executor = new CachingExecutor((Executor)executor);
}
Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
return executor;
}
已分析SimpleExecutor为例,上面我们看到调用映射器中的查询方法最终调用到SqlSession的查询selectList1
2
3
4
5
6
7
8
9
10
11
12public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
try {
MappedStatement ms = this.configuration.getMappedStatement(statement);
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception var9) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);
} finally {
ErrorContext.instance().reset();
}
return var5;
}
这里获取该名字对应MappedStatement结点,执行executor的query方法,最终调用到真正的执行查询的方法doQuery1
2
3
4
5
6
7
8
9
10
11
12
13
14public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = this.prepareStatement(handler, ms.getStatementLog());
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
可以看到,创建一个数据库会话器StatementHandler,执行StatementHandler的prepareStatement方法,进行初始化操作1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Connection connection = this.getConnection(statementLog);
Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
//prepare方法
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(this.boundSql.getSql());
Statement statement = null;
try {
statement = this.instantiateStatement(connection);
this.setStatementTimeout(statement, transactionTimeout);
this.setFetchSize(statement);
return statement;
} catch (SQLException var5) {
this.closeStatement(statement);
throw var5;
} catch (Exception var6) {
this.closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + var6, var6);
}
}
//parameterize方法
public void parameterize(Statement statement) throws SQLException {
this.parameterHandler.setParameters((PreparedStatement)statement);
}
调用prepare方法设置最大行数、超时时间等基本操作,parameterize方法调用参数处理器绑定参数,执行StatementHandler的prepareStatement方法后调用,Executor的query方法1
2
3
4
5public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = this.boundSql.getSql();
statement.execute(sql);
return this.resultSetHandler.handleResultSets(statement);
}
执行对应的语句,并调用结果处理器返回参数
三.总结
在映射器+xml调用中
- 获取映射器,getMapper通过动态代理,获得代理对象
- 在调用相关接口方法时,拦截调用相关方法,用接口名+方法名,在MapperStatements中查找对应的id的MapperStatement,有则,判断该id的xml片断的sql标签,调用sqlsession的对应方法,将接口名+方法名,参数传入
- 调用Executor的query/update方法,创建数据库会话器,调用prepare、parameterize(调用参数处理器绑定参数),query/update(query需要返回封装结果,调用结果处理器。update中返回处理行数,直接返回int类型)
第二点 - 参数处理过程
一.完成参数索引与参数名之间的对应
在动态代理中,通过cachedMapperMethod创建MapperMethod,构建了MethodSignature对象,其中有一个属性为paramNameResolver1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
this.methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
//MapperMethod的构造
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
}
//MethodSignature初始化
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class) {
this.returnType = (Class)resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class)((ParameterizedType)resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
this.returnsVoid = Void.TYPE.equals(this.returnType);
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.returnsCursor = Cursor.class.equals(this.returnType);
this.mapKey = this.getMapKey(method);
this.returnsMap = this.mapKey != null;
this.rowBoundsIndex = this.getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = this.getUniqueParamIndex(method, ResultHandler.class);
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
查看paramNameResolver初始化1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42public class ParamNameResolver {
private static final String GENERIC_NAME_PREFIX = "param";
private final SortedMap<Integer, String> names;
private boolean hasParamAnnotation;
public ParamNameResolver(Configuration config, Method method) {
//存储目标方法的参数对应的Class对象
Class<?>[] paramTypes = method.getParameterTypes();
//存储目标方法的注解对象数组,每一个方法的参数都有一个注解数组
Annotation[][] paramAnnotations = method.getParameterAnnotations();
SortedMap<Integer, String> map = new TreeMap();
//存储目标方法的参数个数
int paramCount = paramAnnotations.length;
for(int paramIndex = 0; paramIndex < paramCount; ++paramIndex) {
if (!isSpecialParameter(paramTypes[paramIndex])) {
String name = null;
Annotation[] var9 = paramAnnotations[paramIndex];
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
Annotation annotation = var9[var11];
if (annotation instanceof Param) {
this.hasParamAnnotation = true;
name = ((Param)annotation).value();
break;
}
}
if (name == null) {
if (config.isUseActualParamName()) {
name = this.getActualParamName(method, paramIndex);
}
if (name == null) {
name = String.valueOf(map.size());
}
}
map.put(paramIndex, name);
}
}
this.names = Collections.unmodifiableSortedMap(map);
}
- isSpecialParameter(paramTypes[paramIndex])判断是否是RowBounds和ResultHandler特殊类型。如果true,则跳过.
- 判断每一个注解是否是Param注解
- true,则hasParamAnnotation赋值为true,表示该方法有@Param注解,然后直接把Param注解的value值赋值给name
- false,没有使用@Param注解,则判断是否配置中开启了useActualParamName
- true,则调用getActualParamName方法,并通过ParamNameUtil工具类获取目标方法的参数名,再把参数名存储到List中,接着根据传入的索引获取对应的参数名.然后把参数索引和参数名存放到map中.
- false,那么就会使用参数索引作为name.
当所有参数都判断之后,通过Collections.unmodifiableSortedMap(map)返回一个只读的Map容器赋值给names,同样存放着参数索引和 参数名的映射关系。此时MethodSignature初始化完毕
useActualParamName在3.4.1开始可以配置true|false。3.4.2之前为false。之后为true
例子:这是在多个参数的时候,才会封装成map,return map。如果只有一个参数则直接返回该参数
Employee findByIdAndNameWithGender(Integer id, @Param("myName")String name, String gender);
- 当useActualParamName()为false时:
0 -> “0”
1 -> “myName”
2 -> “2”- 当useActualParamName()为true时:
0 -> “arg0”
1 -> “myName”
2 -> “arg2”
二.使用对应关系,绑定参数名与具体参数对象
在之前的executeForMany中有这样的一个方法Object param = this.method.convertArgsToSqlCommandParam(args);
具体代码如下
1 | public class MapperMethod { |
- 遍历Map容器(即之前参数索引与参数名对应的map)names,以value作为key和args数组对应索引的值作为value存储到Map容器param中
- 根据GENERIC_NAME_PREFIX常量即”param”和当前的索引拼装成新的字符串,即param1,param2,…,paramN.然后和args数组里对应索引的值存储到Map容器param中
上面举得例子得到的结果
参考文章:https://blog.csdn.net/chenruicsdn/article/details/81370219,这篇博文有一定的错误